// --------------------------------------------------------------------------------------------------------------------
// <copyright file="QueueBackgroundWorker.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Threading;

    using Org.OpenTrader.Framework.Enums;

    #endregion

    /// <summary>
    /// Queue (delegate, call parameters) and call them in FIFO orders (background)
    /// </summary>
    public class QueueBackgroundWorker : IDisposable
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        /// <summary>
        /// The adaptation period.
        /// </summary>
        private const int AdaptationPeriod = 500;

        /// <summary>
        /// The capacity.
        /// </summary>
        private const int Capacity = 32768;

        /// <summary>
        /// The initial long sleep.
        /// </summary>
        private const int InitialLongSleep = MinimumLongSleep + (int)((MaximumLongSleep - MinimumLongSleep) / 2.0);

        /// <summary>
        /// The initial short sleep.
        /// </summary>
        private const int InitialShortSleep = MinimumShortSleep + (int)((MaximumShortSleep - MinimumShortSleep) / 2.0);

        /// <summary>
        /// The maximum long sleep.
        /// </summary>
        private const int MaximumLongSleep = 333;

        /// <summary>
        /// The maximum short sleep.
        /// </summary>
        private const int MaximumShortSleep = 100;

        /// <summary>
        /// The minimum long sleep.
        /// </summary>
        private const int MinimumLongSleep = 10;

        /// <summary>
        /// The minimum short sleep.
        /// </summary>
        private const int MinimumShortSleep = 1;

        /// <summary>
        /// The tasks.
        /// </summary>
        private readonly Queue<Task> tasks;

        /// <summary>
        /// The thread.
        /// </summary>
        private readonly Thread thread;

        /// <summary>
        /// The average task ms.
        /// </summary>
        private double averageTaskMS;

        /// <summary>
        /// The exit.
        /// </summary>
        private bool exit;

        /// <summary>
        /// The last adapted.
        /// </summary>
        private DateTime lastAdapted;

        /// <summary>
        /// The long sleep.
        /// </summary>
        private int longSleep = InitialLongSleep;

        /// <summary>
        /// The short sleep.
        /// </summary>
        private int shortSleep = InitialShortSleep;

        /// <summary>
        /// The tasks executed.
        /// </summary>
        private int tasksExecuted;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="QueueBackgroundWorker"/> class.
        /// </summary>
        public QueueBackgroundWorker()
        {
            Logging.Log(Identity, ELogLevel.Info, () => "QueueBackgroundWorker: Starting a new instance");
            this.tasks = new Queue<Task>(Capacity);
            this.exit = false;
            this.thread = new Thread(this.PumpDoWork);
            this.thread.Name = "QueueBackgroundWorker";
            this.thread.Priority = ThreadPriority.AboveNormal;
            this.thread.Start();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The enqueue.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        public void Enqueue(Task task)
        {
            this.AdaptToWorkload();
            lock (this.tasks)
            {
                this.tasks.Enqueue(task);
            }
        }

        /// <summary>
        /// The terminate.
        /// </summary>
        public void Terminate()
        {
            this.exit = true;
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The adapt to workload.
        /// </summary>
        private void AdaptToWorkload()
        {
            if ((Timings.Now - this.lastAdapted).TotalMilliseconds < AdaptationPeriod)
            {
                return;
            }

            this.lastAdapted = Timings.Now;

            if (this.tasks.Count > Capacity / 2)
            {
                Logging.Log(Identity, ELogLevel.Info, () => "AdaptToWorkload: Queue is above 1/2th of its capacity. Increasing agressivity");
                this.shortSleep = (int)Math.Max(this.shortSleep * 0.90, MinimumShortSleep);
                this.longSleep = MinimumLongSleep;
                return;
            }

            if (this.tasks.Count > Capacity / 4)
            {
                Logging.Log(Identity, ELogLevel.Info, () => "AdaptToWorkload: Queue is above 1/4th of its capacity. Increasing agressivity");
                this.shortSleep = (int)Math.Max(this.shortSleep * 0.95, MinimumShortSleep);
                this.longSleep = MinimumLongSleep;
                return;
            }

            if (this.tasks.Count > Capacity / 16)
            {
                Logging.Log(Identity, ELogLevel.Info, () => "AdaptToWorkload: Queue is above 1/16th of its capacity. Increasing agressivity");
                this.shortSleep = (int)Math.Min(this.shortSleep * 0.99, MaximumShortSleep);
                this.longSleep = (int)Math.Min(this.longSleep * 0.99, MaximumLongSleep);
                return;
            }

            if (this.shortSleep != InitialShortSleep || this.longSleep != InitialLongSleep)
            {
                Logging.Log(Identity, ELogLevel.Info, () => "AdaptToWorkload: Queue is now in an unstressed state");
            }

            this.shortSleep = InitialShortSleep;
            this.longSleep = InitialLongSleep;
            return;
        }

        /// <summary>
        /// The force pump.
        /// </summary>
        private void ForcePump()
        {
            lock (this.tasks)
            {
                while (this.tasks.Count > 0)
                {
                    var nextTask = this.tasks.Dequeue();
                    try
                    {
                        // Console.Out.WriteLine("Force Run " + nextTask.Name );
                        nextTask.Run();
                    }
                    catch
                    {
                        // Console.Error.WriteLine(e);
                    }

                    // Thread.Sleep(MinimumShortSleep/4);
                }
            }
        }

        /// <summary>
        /// The thread.
        /// </summary>
        private void Pump()
        {
            lock (this.tasks)
            {
                while (this.tasks.Count > 0)
                {
                    this.AdaptToWorkload();

                    var v1 = Timings.Now;
                    var task = this.tasks.Dequeue();

                    // var v1 = Timings.BenchmarkNow;
                    // Invoke task
                    try
                    {
                        // Stdout.WriteLine(string.Format("{0} QueueBackgroundWorker: Running {1}", PrettyPrinter.Timings.Format(Timings.Now), nextTask.Name)); 
                        task.Run();
                    }
                    catch (Exception ex)
                    {
                        Logging.Log(Identity, ELogLevel.Error, () => string.Format("Got exception calling nextTask.Run() task={0}", task.Name), ex);
                    }

                    var dt = (Timings.Now - v1).TotalMilliseconds;
                    this.averageTaskMS = ((this.tasksExecuted * this.averageTaskMS) + dt) / (this.tasksExecuted + 1);
                    this.tasksExecuted++;

                    // Timings.Benchmark(nextTask.Name,v1);

                    // Sleep a bit between each task
                    if (this.shortSleep > 1)
                    {
                        Thread.Sleep(this.shortSleep);
                    }
                }
            }
        }

        /// <summary>
        /// The pump do work.
        /// </summary>
        private void PumpDoWork()
        {
            while (!this.exit)
            {
                this.Pump();

                // Adapt sleeping times to workload
                this.AdaptToWorkload();

                // Sleep a bit waiting for queue to have some elements available
                if (this.longSleep > 1)
                {
                    Thread.Sleep(this.longSleep);
                }
            }

            // Finish the queue before exit
            this.ForcePump();
        }

        #endregion

        /// <summary>
        /// The task.
        /// </summary>
        public abstract class Task
        {
            #region Properties

            /// <summary>
            /// Gets Name.
            /// </summary>
            public abstract string Name { get; }

            #endregion

            #region Public Methods

            /// <summary>
            /// The run.
            /// </summary>
            public abstract void Run();

            #endregion
        }
    }
}